Grace Murray Hopper popularised the term bug after in 1947 her team traced an error in the Mark II to a moth trapped in a relay.
even_or_odd <- function(num) {
if (num %% 2 == 0) {
return("even")
} else {
return("odd")
}
}
even_or_odd(42.7)
[1] "odd"
even_or_odd('42')
Error in num%%2: non-numeric argument to binary operator
Traceback:
1. even_or_odd("42")
even_or_odd <- function(num) {
num <- as.integer(num) # We expect input to be integer or convertible into one
if (num %% 2 == 0) {
return("even")
} else {
return("odd")
}
}
even_or_odd(42.7)
[1] "even"
even_or_odd('42')
[1] "even"
Fixing a buggy program is a process of confirming, one by one, that the many things you believe to be true about the code actually are true. When you find that one of your assumptions is not true, you have found a clue to the location (if not the exact nature) of a bug.
Norman Matloff
When you have eliminated all which is impossible, then whatever remains, however improbable, must be the truth.
Arthur Conan Doyle
print()¶print() statement can be used to check the internal state of a program during evaluationls() (and get()/mget()) to reveal all local objectsdebug()/debugonce())calculate_median <- function(a) {
a <- sort(a)
n <- length(a)
m <- (n + 1) %/% 2
if (n %% 2 == 1) {
med <- a[m]
} else {
med <- mean(a[m:m+1])
}
return(med)
}
v1 <- c(1, 2, 3)
v2 <- c(0, 1, 2, 2)
calculate_median(v1)
[1] 2
calculate_median(v2)
[1] 2
print() example¶calculate_median <- function(a) {
a <- sort(a)
n <- length(a)
m <- (n + 1) %/% 2
print(m)
if (n %% 2 == 1) {
med <- a[m]
} else {
med <- mean(a[m:m+1])
}
return(med)
}
# v1 <- c(1, 2, 3)
calculate_median(v1)
[1] 2
[1] 2
# v2 <- c(0, 1, 2, 2)
calculate_median(v2)
[1] 2
[1] 2
print() and ls() example¶calculate_median <- function(a) {
a <- sort(a)
n <- length(a)
m <- (n + 1) %/% 2
# Analogous to Python's print(vars())
# Print all objects in function environment
print(mget(ls()))
if (n %% 2 == 1) {
med <- a[m]
} else {
med <- mean(a[m:m+1])
}
return(med)
}
calculate_median(v1)
$a [1] 1 2 3 $m [1] 2 $n [1] 3
[1] 2
print() and ls() example continued¶calculate_median(v2)
$a [1] 0 1 2 2 $m [1] 2 $n [1] 4
[1] 2
print() example continued¶calculate_median <- function(a) {
a <- sort(a)
n <- length(a)
m <- (n + 1) %/% 2
if (n %% 2 == 1) {
med <- a[m]
} else {
print(m-1:m)
med <- mean(a[m:m+1])
}
return(med)
}
calculate_median(v1)
[1] 2
calculate_median(v2)
[1] 1 0
[1] 2
calculate_median <- function(a) {
a <- sort(a)
n <- length(a)
m <- (n + 1) %/% 2
if (n %% 2 == 1) {
med <- a[m]
} else {
med <- mean(a[m:(m+1)])
}
return(med)
}
calculate_median(v1)
[1] 2
calculate_median(v2)
[1] 1.5
browser() - pauses the execution at a dedicated line in code (breakpoint)debug()/undebug() - (un)sets a flag to run function in a debug mode (setting through)debugonce() - triggers single stepping through a functioncalculate_median <- function(a) {
a <- sort(a)
n <- length(a)
m <- (n + 1) %/% 2
if (n %% 2 == 1) {
med <- a[m]
} else {
browser()
med <- mean(a[m:m+1])
}
return(med)
}
## Example for running in RStudio
calculate_median(v2)
Called from: calculate_median(v2) debug at <text>#9: med <- mean(a[m:m + 1]) debug at <text>#11: return(med)
[1] 2
| Command | Description |
|---|---|
n(ext) |
Execute next line of the current function |
s(tep) |
Execute next line, stepping inside the function (if present) |
c(ontinue) |
Continue execution, only stop when breakpoint in encountered |
f(inish) |
Finish execution of the current loop or function |
Q(uit) |
Quit from the debugger, executed program is aborted |
Extra: Hadley Wickham - Conditions
42 + "ab" # Throws an error
Error in 42 + "ab": non-numeric argument to binary operator Traceback:
as.numeric(c("42", "55.3", "ab", "7")) # Triggers a warning
Warning message in eval(expr, envir, enclos): “NAs introduced by coercion”
[1] 42.0 55.3 NA 7.0
library("dplyr") # Shows a message
Attaching package: ‘dplyr’
The following objects are masked from ‘package:stats’:
filter, lag
The following objects are masked from ‘package:base’:
intersect, setdiff, setequal, union
stop("Error message")
Error in eval(expr, envir, enclos): Error message
Traceback:
1. stop("Error message")
warning("Warning message")
Warning message in eval(expr, envir, enclos): “Warning message”
message("Message")
Message
stop()if (c(TRUE, TRUE, FALSE)) {
print("This used to work pre R-4.2.0")
}
Error in if (c(TRUE, TRUE, FALSE)) {: the condition has length > 1
Traceback:
# Will become an error in future versions of R
c(TRUE, FALSE) && c(TRUE, TRUE)
Warning message in c(TRUE, FALSE) && c(TRUE, TRUE): “'length(x) = 2 > 1' in coercion to 'logical(1)'” Warning message in c(TRUE, FALSE) && c(TRUE, TRUE): “'length(x) = 2 > 1' in coercion to 'logical(1)'”
[1] TRUE
anscombes_quartet <- readr::read_csv("../data/anscombes_quartet.csv")
Rows: 44 Columns: 3 ── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Delimiter: "," chr (1): dataset dbl (2): x, y ℹ Use `spec()` to retrieve the full column specification for this data. ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
try() for errorssuppressWarnings() for warningssuppressMessages() for messagessuppressMessages(anscombes_quartet <- readr::read_csv("../data/anscombes_quartet.csv"))
# But some functions would provide arguments to silence messages
# This should be preferred to heavy-handed suppressMessages()
anscombes_quartet <- readr::read_csv(
"../data/anscombes_quartet.csv",
show_col_types = FALSE
)
# suppressPackageStartupMessages() - a variant for package startup messages
# But suppressMessages() would also work.
suppressPackageStartupMessages(library("dplyr"))
f1 <- function(x) {
log(x)
10
}
f1("x")
Error in log(x): non-numeric argument to mathematical function
Traceback:
1. f1("x")
f2 <- function(x) {
try(log(x))
10
}
f2("y")
Error in log(x) : non-numeric argument to mathematical function
[1] 10
tryCatch() define exiting handlerswithCallingHandlers() define calling handlerstryCatch(
error = function(cnd) {
# code to run when error is thrown
},
code_to_run_while_handlers_are_active
)
withCallingHandlers(
warning = function(cnd) {
# code to run when warning is signalled
},
message = function(cnd) {
# code to run when message is signalled
},
code_to_run_while_handlers_are_active
)
tryCatch() are called exiting handlers.f3 <- function(x) {
tryCatch(
error = function(e) NA,
log(x)
)
}
f3("x")
[1] NA
# Infinite loop, analogous to while (TRUE)
repeat {
num <- readline("Please, enter a number:")
if (num != "") {
withCallingHandlers(
warning = function(cnd) {
print("This is not a number. Please, try again.")
},
num <- as.numeric(num)
)
} else {
print("No input provided. Please, try again.")
}
if (!is.na(num)) {
print(paste0("Your input '", as.character(num), "' is recorded"))
break
}
}
Please, enter a number:f [1] "This is not a number. Please, try again."
Warning message in withCallingHandlers(warning = function(cnd) {:
“NAs introduced by coercion”
Please, enter a number:43 [1] "Your input '43' is recorded"
calculate_median <- function(a) {
if (!is.numeric(a)) {
stop("Vector must be numeric")
}
a <- sort(a)
n <- length(a)
m <- (n + 1) %/% 2
if (n %% 2 == 1) {
med <- a[m]
} else {
med <- mean(a[m:(m+1)])
}
return(med)
}
== (or !=).v3 <- c(7.22, 1.54, 3.47, 2.75)
calculate_median(v3)
[1] 3.11
calculate_median(v3) == 3.11
[1] FALSE
all.equal(calculate_median(v3), 3.11)
[1] TRUE
TRUE or FALSE is expected is to use special functions:all.equal() - approximately equalidentical() - exactly identical (incl. type)isTRUE() - whether value is TRUEisFALSE() - whether value is FALSEall.equal(length(calculate_median(v3)), 1)
[1] TRUE
# Note that the output of length is of type integer
identical(length(calculate_median(v3)), 1)
[1] FALSE
identical(length(calculate_median(v3)), 1L)
[1] TRUE
testthat library Extra: Hadley Wickham - Testing
library("testthat")
Attaching package: ‘testthat’
The following object is masked from ‘package:dplyr’:
matches
calculate_median <- function(a) {
if (!is.numeric(a)) {
stop("Vector must be numeric")
}
a <- sort(a)
n <- length(a)
m <- (n + 1) %/% 2
if (n %% 2 == 1) {
med <- a[m]
} else {
med <- mean(a[m:(m+1)])
}
return(med)
}
testthat::test_that("The length of result is 1", {
testthat::expect_equal(
length(calculate_median(c(0, 1, 2, 2))),
1L
)
testthat::expect_equal(
length(calculate_median(c(1, 2, 3))),
1L
)
testthat::expect_equal(
length(calculate_median(c(7.22, 1.54, 3.47, 2.75))),
1L
)
})
Test passed 😀
testthat::test_that("Error on non-numeric input", {
testthat::expect_error(
calculate_median(c("a", "bc", "xyz"))
)
testthat::expect_error(
calculate_median(c(TRUE, FALSE, FALSE))
)
testthat::expect_error(
calculate_median(c("0", "1", "2", "2"))
)
})
Test passed 🥇
testthat::test_that("The result is numeric", {
testthat::expect_true(
is.numeric(calculate_median(c(0, 1, 1, 2)))
)
testthat::expect_true(
is.numeric(calculate_median(c(1, 2, 3)))
)
testthat::expect_true(
is.numeric(calculate_median(c("a", "bc", "xyz")))
)
})
── Error (<text>:4:3): The result is numeric ─────────────────────────────────── Error in `calculate_median(c("a", "bc", "xyz"))`: Vector must be numeric Backtrace: 1. testthat::expect_true(...) 4. global calculate_median(c("a", "bc", "xyz"))
Error: ! Test failed Traceback: 1. testthat::test_that("The result is numeric", { . testthat::expect_true(is.numeric(calculate_median(c(0, 1, . 1, 2)))) . testthat::expect_true(is.numeric(calculate_median(c(1, 2, . 3)))) . testthat::expect_true(is.numeric(calculate_median(c("a", . "bc", "xyz")))) . }) 2. (function (envir) . { . handlers <- get_handlers(envir) . errors <- list() . for (handler in handlers) { . tryCatch(eval(handler$expr, handler$envir), error = function(e) { . errors[[length(errors) + 1]] <<- e . }) . } . attr(envir, "withr_handlers") <- NULL . for (error in errors) { . stop(error) . } . })(<environment>)